// Copyright (C) 2009 Jesse Jones
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using MmmDoc;
using Mono.Cecil;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MmmDoc.Database
{
	/// <summary>One of the types for which we are generating documentation.</summary>
	internal sealed class TypeDef : TypeName
	{
		/// <remarks>The cookie is used to ensure we're only created by <see cref = "Database.Document"/>.</remarks>
		public TypeDef(Document doc, TypeDefinition type, object cookie) : base(doc, type)
		{
			Contract.Requires(doc.IsValidCookie(cookie));
			
			m_type = type;
		}
		
		/// <remarks>The cookie is used to ensure we're only initialized by <see cref = "Database.Document"/>.</remarks>
		// Need this two stage construction in case a member refers to us.
		public void Init(Document doc, object cookie)
		{
			Contract.Requires(doc.IsValidCookie(cookie));
			
			if (m_type.DeclaringType != null)
				DeclaringType = TypeName.Create(doc, m_type.DeclaringType) as TypeDef;
			Attributes = DoGetAttributes();
			GenericArgs = DoGetGenericArgs(doc);
			BaseTypes = DoGetBaseTypes(doc);
			Members = DoGetMembers(doc);
			NestedTypes = DoGetNestedTypes(doc);
			IsInterface = m_type.IsInterface;
			IsStruct = m_type.IsValueType;
			IsEnum = m_type.IsEnum;
			if (m_type.BaseType != null)
				IsDelegate = m_type.IsSealed && m_type.BaseType.FullName == "System.MulticastDelegate";
			
			var attrs = new List<string>();
			m_type.CustomAttributes.AddAttributes(attrs);
			foreach (SecurityDeclaration sec in m_type.SecurityDeclarations)
			{
				attrs.Add(sec.ToText(false));
			}
			if (m_type.IsSerializable)
				attrs.Add("[Serializable]");
			CustomAttributes = attrs.ToArray();
			
			DoDisambiguate();
			
			Array.Sort(Members, (lhs, rhs) =>
			{
				Member.SortKey key1 = lhs.GetSortKey();
				Member.SortKey key2 = rhs.GetSortKey();
				return key1.CompareTo(key2);
			});
		}
		
		/// <summary>The type this type is declared within (or null).</summary>
		public TypeDef DeclaringType {get; private set;}
		
		public string[] CustomAttributes {get; private set;}
		
		/// <summary>Public, sealed, class, struct, etc.</summary>
		public string Attributes {get; private set;}
		
		/// <summary>Null if the type is not generic.</summary>
		/// <remarks>These may be or may not be instantiated.</remarks>
		public TypeName[] GenericArgs {get; private set;}
		
		/// <summary>Super class and interfaces.</summary>
		/// <remarks>The first type may or may not be an interface. The remaining types will be interfaces.</remarks>
		public TypeName[] BaseTypes {get; private set;}
		
		/// <summary>These are the immediate children.</summary>
		public TypeDef[] DerivedTypes {get {return m_derived.ToArray();}}
		
		/// <summary>Each member of the type for which we are generating documentation.</summary>
		public Member[] Members {get; private set;}
		
		/// <summary>These are the immediate nested types.</summary>
		public TypeName[] NestedTypes {get; private set;}
		
		public bool IsInterface {get; private set;}
		
		public bool IsStruct {get; private set;}
		
		public bool IsEnum {get; private set;}
		
		public bool IsDelegate {get; private set;}
		
		public bool IsExcluded()
		{
			bool excluded = false;
			
			if (Definition != null)
			{
				if (Definition.Excluded)
				{
					excluded = true;
				}
				else if (Name == "NamespaceDoc")
				{
					excluded = true;
				}
				else if (Definition.ExcludeIfEmpty)
				{
					excluded = Members.All(m => m.Definition != null && m.Definition.Excluded);
				}
			}
			
			return excluded;
		}
		
		#region Private Methods
		private string DoGetAttributes()
		{
			var builder = new StringBuilder();
			
			TypeAttributes attrs = m_type.Attributes;
			switch (attrs & TypeAttributes.VisibilityMask)
			{
				case TypeAttributes.NotPublic:
				case TypeAttributes.NestedAssembly:
					builder.Append("internal ");
					break;
					
				case TypeAttributes.Public:
				case TypeAttributes.NestedPublic:
					builder.Append("public ");
					break;
					
				case TypeAttributes.NestedPrivate:
					builder.Append("private ");
					break;
					
				case TypeAttributes.NestedFamily:
					builder.Append("protected ");
					break;
					
				case TypeAttributes.NestedFamANDAssem:
					builder.Append("protected&internal ");
					break;
					
				case TypeAttributes.NestedFamORAssem:
					builder.Append("protected internal ");
					break;
					
				default:
					Contract.Assert(false, "bad visibility: " + (attrs & TypeAttributes.VisibilityMask));
					break;
			}
			
			if ((attrs & TypeAttributes.ClassSemanticMask) != TypeAttributes.Interface && !m_type.IsValueType)
			{
				if ((attrs & TypeAttributes.Abstract) == TypeAttributes.Abstract && (attrs & TypeAttributes.Sealed) == TypeAttributes.Sealed)
					builder.Append("static ");
				
				else if ((attrs & TypeAttributes.Abstract) == TypeAttributes.Abstract)
					builder.Append("abstract ");
				
				else if ((attrs & TypeAttributes.Sealed) == TypeAttributes.Sealed)
					builder.Append("sealed ");
			}
			
			if ((attrs & TypeAttributes.ClassSemanticMask) == TypeAttributes.Interface)
				builder.Append("interface ");
			else if (m_type.IsEnum)
				builder.Append("enum ");
			else if (m_type.BaseType != null && m_type.IsSealed && m_type.BaseType.FullName == "System.MulticastDelegate")
				builder.Append("delegate ");
			else if (m_type.IsValueType)
				builder.Append("struct ");
			else if (m_type.IsValueType)
				builder.Append("struct ");
			else
				builder.Append("class ");
				
			return builder.ToString();
		}
		
		private TypeName[] DoGetGenericArgs(Document doc)
		{
			TypeName[] args = null;
			
			if (m_type.HasGenericParameters)
			{
				args = new TypeName[m_type.GenericParameters.Count];
				for (int i = 0; i < m_type.GenericParameters.Count; ++i)
				{
					args[i] = TypeName.Create(doc, m_type.GenericParameters[i]);
				}
			}
			
			return args;
		}
		
		private TypeName[] DoGetBaseTypes(Document doc)
		{
			var bases = new List<TypeName>();
			
			if (m_type.BaseType != null && m_type.BaseType.FullName != "System.Object")
			{
				bases.Add(TypeName.Create(doc, m_type.BaseType));
			}
			
			if (m_type.HasInterfaces)
			{
				foreach (TypeReference tr in m_type.Interfaces)
				{
					bases.Add(TypeName.Create(doc, tr));
				}
			}
			
			foreach (TypeName b in bases)
			{
				TypeDef td = b as TypeDef;
				if (td != null)
					td.m_derived.Add(this);
			}
			
			return bases.ToArray();
		}
		
		private Member[] DoGetMembers(Document doc)
		{
			var members = new List<Member>();
			
			if (m_type.HasConstructors)
			{
				for (int i = 0; i < m_type.Constructors.Count; ++i)
				{	
					if (doc.IsVisible(m_type.Constructors[i]))
						members.Add(new ConstructorMember(doc, this, m_type.Constructors[i]));
				}
			}
			
			if (m_type.HasEvents)
			{
				for (int i = 0; i < m_type.Events.Count; ++i)
				{
					EventDefinition e = m_type.Events[i];
					
					if (doc.IsVisible(e.AddMethod) || doc.IsVisible(e.InvokeMethod) || doc.IsVisible(e.RemoveMethod))
						members.Add(new EventMember(doc, this, e));
				}
			}
			
			if (m_type.HasFields)
			{
				for (int i = 0; i < m_type.Fields.Count; ++i)
				{
					if (doc.IsVisible(m_type.Fields[i]))
						members.Add(new FieldMember(doc, this, m_type.Fields[i]));
				}
			}
			
			if (m_type.HasMethods)
			{
				for (int i = 0; i < m_type.Methods.Count; ++i)
				{	
					MethodDefinition m = m_type.Methods[i];
					
					if (m.SemanticsAttributes == 0)
						if (doc.IsVisible(m))
							members.Add(new MethodMember(doc, this, m));
				}
			}
			
			if (m_type.HasProperties)
			{
				for (int i = 0; i < m_type.Properties.Count; ++i)
				{
					PropertyDefinition p = m_type.Properties[i];
					
					if (doc.IsVisible(p.GetMethod) || doc.IsVisible(p.SetMethod))
					{
						members.Add(new PropertyMember(doc, this, p));
					}
				}
			}
			
			return members.ToArray();
		}
		
		private TypeName[] DoGetNestedTypes(Document doc)
		{
			var types = new List<TypeName>();
			
			if (m_type.HasNestedTypes)
			{
				foreach (TypeDefinition type in m_type.NestedTypes)
				{
					types.Add(TypeName.Create(doc, type));
				}
			}
			
			return types.ToArray();
		}
		
		private void DoDisambiguate()
		{
			for (int i = 0; i < Members.Length - 1; ++i)
			{
				string key = Members[i].Key;
				
				bool ambiguous = false;
				for (int j = i + 1; j < Members.Length; ++j)
				{
					if (Members[j].Key == key)
					{
						Members[j].Disambiguate();
						ambiguous = true;
					}
					
					if (ambiguous)
						Members[i].Disambiguate();
				}
			}
		}
		#endregion
		
		#region Fields
		private TypeDefinition m_type;
		private List<TypeDef> m_derived = new List<TypeDef>();
		#endregion
	}
}
